package at.cookieforensics;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Date;

import org.tmatesoft.sqljet.core.SqlJetException;
import org.tmatesoft.sqljet.core.SqlJetTransactionMode;
import org.tmatesoft.sqljet.core.table.ISqlJetCursor;
import org.tmatesoft.sqljet.core.table.ISqlJetTable;
import org.tmatesoft.sqljet.core.table.SqlJetDb;

import com.gargoylesoftware.htmlunit.util.Cookie;

/**
 * a class which handles reading of SQLite Files (Chrome, Firefox)
 * 
 * @author Martin Grottenthaler
 *
 */
public class SQLiteRead {
	/**
	 * tablename in chrome cookie database file
	 */
	private static final String chromeTable = "cookies";
	/**
	 * tablename in firefox cookie database file
	 */
	private static final String firefoxTable = "moz_cookies";
	
	/**
	 * collumn names in chrome cookie database file
	 */
	private static final String[] chromeCollumn = {"name", "encrypted_value", "expires_utc", "path", "host_key", "secure", "httponly"};
	/**
	 * collumn names in firefox cookie database file
	 */
	private static final String[] firefoxCollumn = {"name", "value", "expiry", "path", "baseDomain", "isSecure", "isHttpOnly"};
	
	/**
	 * ArrayList of cookies
	 */
	private static ArrayList<Cookie> cookies;
	
	/**
	 * operating system
	 */
	private static String os = null;
	/**
	 * browser, 1 -> chrome, 2 -> firefox
	 */
	private static int browser;
	
	/**
	 * Reads cookies from Chrome and Firefox
	 * @param osIn OS as a string, possible values: linux, windows
	 * @param browserIn Browser type as int, possible values: 1 -> chrome, 2 -> firefox
	 * @return ArrayList of cookies
	 */
	public static ArrayList<Cookie> readSQLite(String osIn, int browserIn) {
		cookies = new ArrayList<Cookie>();
		
		os = osIn;
		browser = browserIn;
		
		prepareFile();
		
		File dbFile = null;
		
		if (browser == 1) {
			dbFile = new File("chrome.sqlite");
		}
		
		if (browser == 2) {
			dbFile = new File("firefox.sqlite");
		}
		
		
        SqlJetDb db = null;
		try {
			db = SqlJetDb.open(dbFile, false);
		} catch (SqlJetException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
		
		try {
			ISqlJetTable table = null;
			if(browser == 1) {
				table = db.getTable(chromeTable);
			}
			
			if(browser == 2) {
				table = db.getTable(firefoxTable);
			}
			db.beginTransaction(SqlJetTransactionMode.READ_ONLY);
			readRecords(table.order(table.getPrimaryKeyIndexName()));
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally {
			try {
				db.commit();
			} catch (SqlJetException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
			
		try {
			db.close();
		} catch (SqlJetException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		return cookies;
	}
	
    /**
     * Reads records from SQLite file
     * @param cursor
     */
    private static void readRecords(ISqlJetCursor cursor) throws Exception {
    	String[] cookiesStr = new String[7];
    	
        try {
            if (!cursor.eof()) {
                do {
                	Date expires = new Date();;
                	boolean secure = false;
                	boolean httpOnly = false;
                	
                	// expiry date
                	// chrome: ldap time (if 17 digits add 1 random digit(use 0 out of convenience))
                	// firefox: unix time (sometimes unix time in microseconds)
                	
                	if(browser == 1) {
                		for (int i = 0; i < chromeCollumn.length; i++) {
    						cookiesStr[i] = cursor.getString(chromeCollumn[i]);
    					}
                		
                		cookiesStr[2] = cookiesStr[2].concat("0");
                		long timeNoC = Long.parseLong(cookiesStr[2]);
                		
                		// source: http://www.epochconverter.com/epoch/ldap-timestamp.php
                		// formula 1: LDAP time = (time()+11644473600)*10000000
                		// formala 2: LDAP time / 10000000 - 11644473600 = time()
                		timeNoC = timeNoC / 10000000L - 11644473600L;
                		
                		expires.setTime(timeNoC*1000);
                		
                		/*
                		// Testing
                		File tmpEncrypted = new File("tmpEncrypted");
                		BufferedWriter bw = new BufferedWriter(new FileWriter(tmpEncrypted));
                		bw.write(cookiesStr[1]);
                		bw.close();
                		
                		// decrypt cookie value
                		byte[] ciphertext = (cookiesStr[1].substring(3)).getBytes("UTF-8");
                		
                		byte[] salt = "saltysalt".getBytes("UTF-8");
                		byte[] iv = StringUtils.repeat(' ', 16).getBytes("UTF-8");
                		int length = 128;
                		char[] pass = "peanuts".toCharArray();
                		int iterations = 1;
                		
                		SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
                		KeySpec spec = new PBEKeySpec(pass, salt, iterations, length);
                		SecretKey tmp = factory.generateSecret(spec);
                		SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");
                		
                		Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
                		cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(iv));
                		
                		byte[] plaintext = cipher.doFinal(ciphertext);
                		System.out.println(plaintext);*/
                	}
                	
                	if(browser == 2) {
                		for (int i = 0; i < firefoxCollumn.length; i++) {
                			cookiesStr[i] = cursor.getString(firefoxCollumn[i]);
    					}
                		
                		expires.setTime(Long.parseLong(cookiesStr[2])*1000);
                	}
                	
                	if (!cookiesStr[5].equals(0)) {
						secure = true;
					}
                	
                	if (!cookiesStr[6].equals(0)) {
						httpOnly = true;
					}
                	
                	cookies.add(new Cookie(cookiesStr[4], cookiesStr[0], cookiesStr[1], cookiesStr[3], expires, secure, httpOnly));
                } while(cursor.next());
            }
        } finally {
            cursor.close();
        }
    }
    
    /**
     * Prepares cookie files. Copies them to working directory. In case of firefox change file format because this version of
     * SQLJet doesn't support wal format.
     */
    private static void prepareFile() {
    	File source = null;
    	File destination = null;
    	
    	if (os.equals("Linux")) {
    		if (browser == 1) {
				source = new File(System.getProperty("user.home") + File.separator + ".config" + File.separator + "google-chrome"
						+ File.separator + "Default" + File.separator + "Cookies");
				destination = new File("chrome.sqlite");
			}
    		
    		if (browser == 2) {
    			// ~/.mozilla/firefox/*.default/cookies.sqlite
    			File folder = new File(System.getProperty("user.home") + File.separator + ".mozilla" + File.separator + "firefox");
    			File[] fileList = folder.listFiles();
    			
    			String profile = null;
    			
    			for (int i = 0; i < fileList.length; i++) {
    				File file = fileList[i];
    				
    				if (file.getName().contains(".default")) {
    					profile = file.getName();
    				}
    			}
    			
    			source = new File(folder.getPath() + File.separator + profile + File.separator + "cookies.sqlite");
    			destination = new File("firefox.sqlite");
			}
    	}
    
    	// Done Windows 7/8 Paths
    	if (os.contains("Windows")) {
    		if (browser == 1) {
    			if(os.contains("7") || os.contains("8")){
	    			source = new File(System.getProperty("user.home") + "\\AppData\\Local\\Google\\Chrome\\User Data\\Default\\Cookies");
    			}
    			else {
    				source = new File(System.getProperty("user.home") + "\\Local Settings\\Application Data\\Google\\Chrome\\User Data\\Default\\Cookies");
    			}
    			destination = new File("chrome.sqlite");
			}
    		
    		if (browser == 2) {
    			File folder = null;
    			if(os.contains("8") || os.contains("7")){ 
    				folder = new File(System.getProperty("user.home") + "\\AppData\\Roaming\\Mozilla\\Firefox\\Profiles\\");
    			}
    			else { 
    				folder = new File(System.getProperty("user.home") + "\\Application Data\\Mozilla\\Firefox\\Profiles\\");
    			}
				File[] fileList = folder.listFiles();
				String profile = null;
				
				for (int i = 0; i < fileList.length; i++) {
    				File file = fileList[i];
    				
    				if (file.getName().contains(".default")) {
    					profile = file.getName();
    				}
    			}
				source = new File(folder.getPath() + File.separator + profile + File.separator + "cookies.sqlite");
				destination = new File("firefox.sqlite");
			}
    	}
    	
    	if (destination.exists()) {
			destination.delete();
    	}
    	
    	try {
			Files.copy(source.toPath(), destination.toPath());
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		};
		
		// change byte 18 and 19 of file to 01 in case of firefox
		if (browser == 2) {
			RandomAccessFile raf = null;
			try {
				raf = new RandomAccessFile(destination, "rw");
			} catch (FileNotFoundException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
			try {
			    raf.seek(18);
			    raf.write(01);
			    
			    raf.seek(19);
			    raf.write(01);
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} finally {
			    try {
					raf.close();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
		destination.deleteOnExit();
    }
}
